/**
 * 
 */
package org.statewalker.fsm;

import java.util.Collection;
import java.util.List;

import org.statewalker.graph.AbstractNodeWalker;
import org.statewalker.graph.INodeWalkerListener;
import org.statewalker.graph.INodeWalkerSource;
import org.statewalker.graph.NodeWalker;

/**
 * @author kotelnikov
 */
public class FsmProcess<S, E> {

    private E fEvent;

    private INodeWalkerListener<S, RuntimeException> fListener = new INodeWalkerListener<S, RuntimeException>() {

        public boolean beginNode(S parent, S node) {
            List<S> stack = fWalker.getStack();
            fStateListener.beginState(stack, node);
            return true;
        }

        public void endNode(S parent, S node) {
            List<S> stack = fWalker.getStack();
            fStateListener.endState(stack, node);
        }

    };

    protected IFsmProcessListener<S, E> fProcessListener;

    private S fRootState;

    protected IFsmStateListener<S> fStateListener;

    private IFsmStateProvider<S, E> fStateProvider;

    private boolean fTerminated;

    private NodeWalker<S, RuntimeException> fWalker = new NodeWalker<S, RuntimeException>(
        new INodeWalkerSource<S, RuntimeException>() {

            public S getFirstSubnode(S node) {
                if (isTerminated())
                    return null;
                return getNextState(null, fEvent);
            }

            public S getNextSubnode(S parent, S node) {
                if (isTerminated())
                    return null;
                return getNextState(node, fEvent);
            }

        },
        null);

    public FsmProcess(
        IFsmStateProvider<S, E> stateProvider,
        IFsmProcessListener<S, E> processListener,
        IFsmStateListener<S> stateListener,
        S rootState) {
        fStateListener = stateListener;
        fProcessListener = processListener;
        fStateProvider = stateProvider;
        fRootState = rootState;
        fWalker.setNextNode(fRootState);
    }

    protected void copyStateStack(Collection<? super S> stack) {
        stack.addAll(fWalker.getStack());
    }

    /**
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @SuppressWarnings("unchecked")
    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (!(obj instanceof FsmProcess<?, ?>))
            return false;
        FsmProcess<S, E> process = (FsmProcess<S, E>) obj;
        return fRootState.equals(process.fRootState);
    }

    /**
     * Returns the current active state; an active state is a state for which
     * the {@link IFsmStateListener#beginState(List<S>, FsmState)} was called.
     * 
     * @return the current active state
     */
    public S getCurrentState() {
        S x = fWalker.getCurrentNode();
        return x;
    }

    public E getEvent() {
        return fEvent;
    }

    protected S getNextState(S state, E event) {
        List<S> stack = fWalker.getStack();
        S next = fStateProvider.getNextState(stack, state, event);
        if (!stack.isEmpty() && (state != null || next != null))
            fProcessListener.onTransition(stack, state, event, next);
        return next;
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        return fRootState.hashCode();
    }

    public boolean isTerminated() {
        return fTerminated;
    }

    /**
     * @param mask the "debugging" mask
     * @return <code>true</code> if the next step was successfully performed
     */
    public boolean run(AbstractNodeWalker.Mode mode) {
        fTerminated = !fWalker.shift(fListener, mode);
        return !fTerminated;
    }

    public void setEvent(E event) {
        fEvent = event;
    }

    public void setTerminated(boolean terminated) {
        fTerminated = terminated;
    }

    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "Process[" + fRootState + "]";
    }
}
